use crate::macro_helpers::helpers::default_doc;
use crate::{tardis_create_index, tardis_create_table};
use darling::{FromField, FromMeta};
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};

use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Semi;
use syn::{Data, Error, Fields, Result};

#[derive(FromField, Debug, Clone)]
#[darling(attributes(fill_ctx))]
struct FillCtxMeta {
    ident: Option<Ident>,
    /// Specify fill param
    #[darling(default)]
    fill: Fill,
    /// Only fill when insert
    #[darling(default = "true_default")]
    insert_only: bool,
}

#[derive(Clone, Debug, FromMeta, Default)]
#[darling(rename_all = "snake_case")]
enum Fill {
    Ak,
    #[default]
    Owner,
    OwnPaths,
}

fn true_default() -> bool {
    true
}

pub(crate) fn create_entity(ident: Ident, data: Data) -> Result<TokenStream> {
    if ident != "Model" {
        panic!("Struct name must be Model");
    }
    match data.clone() {
        Data::Struct(data_struct) => {
            let doc = default_doc();
            let create_table_stat = tardis_create_table::create_table(ident.clone(), data.clone(), None)?;
            let create_index_stat = tardis_create_index::create_index(ident, data, None)?;

            let (insert_only_fill_ctx_stat, always_fill_ctx_stat) = create_fill_ctx_statement(data_struct.fields)?;
            Ok(quote! {

                #doc
                impl ::tardis::db::reldb_client::TardisActiveModel for ActiveModel {
                    fn fill_ctx(&mut self, ctx: &::tardis::basic::dto::TardisContext, is_insert: bool) {
                        if is_insert {
                            #insert_only_fill_ctx_stat;
                        }
                        #always_fill_ctx_stat;
                    }

                    // Call the method automatically generated by TardisCreateTable macros
                    fn create_table_statement(db: ::tardis::db::sea_orm::DbBackend) -> ::tardis::db::sea_orm::sea_query::TableCreateStatement {
                        tardis_create_table_statement(db)
                    }
                    // Call the method automatically generated by TardisCreateTable macros
                    fn create_index_statement() -> Vec<::tardis::db::sea_orm::sea_query::IndexCreateStatement> {
                        tardis_create_index_statement()
                    }
                }
            #create_table_stat

            #create_index_stat

            })
        }
        Data::Enum(_) => Err(Error::new(ident.span(), "enum is not support!")),
        Data::Union(_) => Err(Error::new(ident.span(), "union is not support!")),
    }
}

/// return (only_insert_statement,always_fill_statement)
fn create_fill_ctx_statement(fields: Fields) -> Result<(TokenStream, TokenStream)> {
    let mut only_insert_stat: Punctuated<TokenStream, Semi> = Punctuated::new();
    let mut always_fill_stat: Punctuated<TokenStream, Semi> = Punctuated::new();
    for field in fields {
        for attr in field.attrs.clone() {
            if let Some(ident) = attr.path().get_ident() {
                if ident == "fill_ctx" {
                    let field_fill_ctx_meta: FillCtxMeta = match FillCtxMeta::from_field(&field) {
                        Ok(field) => field,
                        Err(err) => {
                            return Err(Error::new(attr.span(), err.to_string()));
                        }
                    };

                    let push_fun = |stat: &mut Punctuated<TokenStream, Semi>| match field_fill_ctx_meta.fill {
                        Fill::Ak => {
                            if let Some(ident) = field_fill_ctx_meta.ident {
                                stat.push(quote! {
                                    self.#ident=Set(ctx.ak.to_string())
                                });
                            }
                        }
                        Fill::Owner => {
                            if let Some(ident) = field_fill_ctx_meta.ident {
                                stat.push(quote! {
                                    self.#ident=Set(ctx.owner.to_string())
                                });
                            }
                        }
                        Fill::OwnPaths => {
                            if let Some(ident) = field_fill_ctx_meta.ident {
                                stat.push(quote! {
                                    self.#ident=Set(ctx.own_paths.to_string())
                                });
                            }
                        }
                    };
                    if field_fill_ctx_meta.insert_only {
                        push_fun(&mut only_insert_stat);
                    } else {
                        push_fun(&mut always_fill_stat);
                    }
                }
            }
        }
    }
    Ok((only_insert_stat.into_token_stream(), always_fill_stat.into_token_stream()))
}
